package com.ar.droid;


import java.io.*;
/**
 * Generic Java wrapper to Gnu Privacy Guard
 * 
 * @author <a href="mailto:wsgrah@wm.edu">Wayne Graham</a>
 * @version 0.5
 *
 */
public class GnuPG {
  private String gpgRuntime, gpgResult, gpgError, gpgRun;
  private String GPG_SWITCHES = " --batch --armor --output -";
  private File tmpFile;
  private int exitCode = -1;
  
  GnuPG(){
    this("gpg");
  }//GnuPG
  
  public GnuPG(String gpgRuntime){
    this.gpgRuntime = gpgRuntime + " " + GPG_SWITCHES;
    this.gpgRun = gpgRuntime;
  }//GnuPG
  
  public void init(String gpgRuntime){
    this.gpgRuntime = gpgRuntime + " " + GPG_SWITCHES;
    this.gpgRun = gpgRuntime;
  }//init
  
  /**
   * Set the GnuPG Runtime path
   * @param gpgRuntime Absolute path to GnuPG runtime binaries
   */
  public void setGnuPGRuntime(String gpgRuntime){
    this.gpgRuntime = gpgRuntime + " " + GPG_SWITCHES;
    this.gpgRun = gpgRuntime;
  }//setGnuPGRuntime
  
  /**
   * Return the gpgRuntime variable
   * @return gpgRuntime Path to the GnuPG runtime
   */
  public String getGnuPGRuntime(){
    return this.gpgRuntime;
  }//getGngPGRuntime
  
  /**
   * Get GnuPG version
   * @return GnuPG version
   */
  public String getVersion(){
    boolean success = runGnuPG(" --version", null);
    
    if(success && this.exitCode != 0){
      success = false;
    }
    if (success)
      return gpgResult;
    else
      return gpgError;
  }//getVersion
  
  /**
   * Get GnuPG warranty information.
   * @return GnuPG warranty information.
   */
  public String getWarranty(){
    boolean success = runGnuPG(" --warranty", null);
    
    if(success && this.exitCode != 0){
      success = false;
    }
    if (success)
      return gpgResult;
    else
      return gpgError;
  }//getWarranty
  
  /**
   * Implementation of the experimental --batch creation of keys.
   * Warning: This method takes a few seconds to generate a key.
   * 
   * @param realName Name for the key
   * @param comment Key comment
   * @param email User's email address
   * @param expireDate Key's expiration date. Valid entries include
   *   <ul>
   *     <li>0 --> key does not expire</li>
   *     <li><em>n</em> --> key expires in n days</li>
   *     <li><em>n</em>w --> key expires in n weeks</li>
   *     <li><em>n</em>m --> key expires in n months</li>
   *     <li><em>n</em>y --> key expires in n years</li>
   *   </ul>
   * @param passphrase User's passphrase
   * @return GnuPG data/error stream
   */
  public String newKey(String realName, String comment, String email, String expireDate, String passphrase){
    String fileContents = "Key-Type: DSA\n" +
      "Key-Length: 1024\n" +
      "Subkey-Type: ELG-E\n" +
      "Subkey-Length: 1024\n" +
      "Name-Real: " + realName + "\n"; 
      if(comment.length() > 0){
        fileContents += "Name-Comment: " + comment + "\n";
      }
      fileContents += "Name-Email: " + email + "\n" +
      "Expire-Date: " + expireDate + "\n" +
      "Passphrase: " + passphrase + "\n" +
      "#comment\n%commit\n%echo done";
    
    boolean success = createTempFile(fileContents);
    
    if(success){
      success = runGnuPG(" --gen-key " + this.tmpFile.getAbsolutePath(), null);
      this.tmpFile.delete();
      if(success && this.exitCode != 0){
        success = false;
      }
    }
    if(success)
      return gpgResult;
    else
      return gpgError;
  }//newKey
  
  /**
   * List public keys.
   * @return List of keys on public keyring.
   */
  public String listKeys(){
    boolean success = runGnuPG(" --list-keys", null);
    
    if(success && this.exitCode != 0){
      success = false;
    }
    if (success)
      return gpgResult;
    else
      return gpgError;
  }//listKeys
  
  /**
   * List keys of a specific user
   * @return Specific key on public keyring.
   */
  public String listKeys(String name){
    boolean success = runGnuPG(" --list-keys \"" + name + "\"", null);
    
    if(success && this.exitCode != 0){
      success = false;
    }
    if (success)
      return gpgResult;
    else
      return gpgError;
  }
  
  /**
   * List keys from secret key ring
   * @return Keys on secret keyring.
   */
  public String listSecretKeys(){
    boolean success = runGnuPG(" --list-secret-keys", null);
    
    if(success && this.exitCode != 0){
      success = false;
    }
    if (success)
      return gpgResult;
    else
      return gpgError;
  }//listSecretKeys
  
  /**
   * List specific keys from secret key ring
   * @param keyID
   * @return Specific key from secret key ring.
   */
  public String listSecretKeys(String keyID){
    boolean success = runGnuPG(" --list-secret-keys \"" + keyID + "\"", null);
    
    if(success && this.exitCode != 0){
      success = false;
    }
    if (success)
      return gpgResult;
    else
      return gpgError;
  }//listSecretKeys
  
  /**
   * List all keys with fingerprints
   * @return Fingerprints of keys in public key ring.
   */
  public String finger(){
    boolean success = runGnuPG(" --finger", null);
    
    if(success && this.exitCode != 0){
      success = false;
    }
    if (success)
      return gpgResult;
    else
      return gpgError;
  }//finger
  
  /**
   * List specific key with fingerprint
   * @return Fingerprint of specific key from public key ring.
   */
  public String finger(String name){
    boolean success = runGnuPG(" --finger \"" + name + "\"", null);
    
    if(success && this.exitCode != 0){
      success = false;
    }
    if (success)
      return gpgResult;
    else
      return gpgError;
  }//finger
  
  /**
   * List all signatures
   * @return All signatures in the public key ring.
   */
  public String listSigs(){
    boolean success = runGnuPG(" --list-sigs", null);
    
    if(success && this.exitCode != 0){
      success = false;
    }
    if (success)
      return gpgResult;
    else
      return gpgError;
  }//listSigs
  
  /**
   * Return a public key from the public key ring.
   * @param keyID KeyID to get key for
   * @return Public key for specified keyID
   */
  public String getPublicKey(String keyID){
    boolean success = runGnuPG(" --export \"" + keyID + "\"", null);
    
    if(success && this.exitCode != 0){
      success = false;
    }
    if (success)
      return gpgResult;
    else
      return gpgError;
  }//getPublicKey
  
  /**
   * Import a key
   * @param key Public key to import
   * @return GnuPG output/error stream
   */
  
  public String importKey(String key){
	    boolean success = createTempFile(key);
	    
	    if(success){
	    	String commando=" --import " + this.tmpFile.getAbsolutePath();
	        
	      success = runGnuPG(commando, null);
	      this.tmpFile.delete();
	      if(success && this.exitCode != 0){
	        success = false;
	      }
	    }
	    if(success)
	      return gpgResult;
	    else
	      return gpgError;
	  }//importKey
      
      
      
      
      
      
     
  
  /**
   * Create a signature for a data stream.
   * @param string String to sign.
   * @param passphrase Passphrase for signature.
   * @return Signature contents.
   */
  public String sign(String string, String passphrase){
    boolean success = createTempFile(string); // create a temp file with string contents
    
    if(success){
      success = runGnuPG(" --passphrase-fd 0 --sign " + this.tmpFile.getAbsolutePath(), passphrase);
      this.tmpFile.delete();
      if(success && this.exitCode != 0)
        success = false;
    }//if
    if (success)
      return gpgResult;
    else
      return gpgError;
  }//sign
  
  /**
   * Sign a file
   * @param filePath Path to file
   * @param passphrase Passphrase to sign with
   * @return GnuPG buffer
   */
  public String signFile(String filePath, String passphrase){
    int length = filePath.length() - 4;
    String outputFile = filePath.substring(0, length);
    
    File f = new File(outputFile + ".sig");
    if(f.exists())
      f.delete();
    
    boolean success = runGnuPG(" --output \"" + outputFile + ".sig\" --passphrase-fd 0 --sign " + filePath, passphrase, false);
    
    if(success && this.exitCode != 0){
      success = false;      
    }
    
    if(success){
      return gpgResult;
    }else {
      return gpgError;
    }
  }//signFile
  
  /**
   * Clear sign a data stream. -- signature block not returned.
   * @param string Data stream to sign.
   * @param passphrase Passphrase to produce signature.
   * @return Clear signature for specified data stream.
   */
  public String clearSign(String string, String passphrase){
    boolean success = createTempFile(string);
    
    if(success){
      success = runGnuPG(" --passphrase-fd 0 --clearsign " + this.tmpFile.getAbsolutePath(), passphrase);
      this.tmpFile.delete();
      if(success && this.exitCode != 0)
        success = false;
    }
    if (success)
      return gpgResult;
    else
      return gpgError;
  }//clearSign
  
  /**
   * Sign and encrypt a data stream.
   * @param string Datastream to sign and encrypt
   * @param keyID Key to sign for.
   * @param passphrase Passphrase to encrypt/sign.
   * @return Signed and encrypted data stream
   */
  public String signAndEncrypt(String string, String keyID, String passphrase){
    boolean success = createTempFile(string);
    
    if(success){
      success = runGnuPG(" -r " + keyID + " --passphrase-fd 0 -se " + this.tmpFile.getAbsolutePath(), passphrase);
      this.tmpFile.delete();
      if(success && this.exitCode != 0){
        success = false;
      }
    }
    if (success)
      return gpgResult;
    else
      return gpgError;
  }//signAndEncrypt
  
  /**
   * Verify a signature
   * @param sig Signature to verify
   * @return GnuPG output stream
   */
  public String verifySignature(String sig){
    boolean success = createTempFile(sig);
    
    if(success){
      success = runGnuPG(" --verify " + this.tmpFile.getAbsolutePath(), null);
      this.tmpFile.delete();
      if(success && this.exitCode != 0);{
        success = false;
      }
    }
    if (success)
      return gpgResult;
    else
      return gpgError;
  }//verifySignature
  
  /**
   * Verify a file
   * @param filePath
   * @return
   */
  public String verifyFile(String filePath){
    int length = filePath.length() - 4;
    String file = filePath.substring(0, length);
    
    boolean success = runGnuPG(" --verify " + file + ".sig", null, false);
    
    if(success && this.exitCode != 0)
      success = false;
    
    if(success)
      return gpgResult;
    else
      return gpgError;
  }//verifyFile
  
  /**
   * Encrypt a data stream.
   * @param string Data stream to encrypt.
   * @param keyID KeyID to encrypt for.
   * @return Encrypted data stream.
   */
  public String encrypt(String string, String keyID){
    boolean success = runGnuPG(" -r " + keyID + " --encrypt", string);
    
    if(success && this.exitCode != 0){
      success = false;
    }
    if (success)
      return gpgResult;
    else
      return gpgError;
  }//encrypt
  
  /**
   * Decrypt a data stream.
   * @param string Datastream to decrypt.
   * @param passphrase Passphrase to decrypt with.
   * @return Decrypted data stream.
   */
  public String decrypt(String string, String passphrase){
    boolean success = createTempFile(string);
    
    if(success){
      success = runGnuPG(" --passphrase-fd 0 --decrypt " + this.tmpFile.getAbsolutePath(), passphrase);
      this.tmpFile.delete();
      if(success && this.exitCode != 0){
        success = false;
      }
    }
    if (success)
      return gpgResult;
    else
      return gpgError;
  }//decrypt
  
  /**
   * Return GnuPG result.
   * @return GnuPG result
   */
  public String getResult(){
    return gpgResult;
  }//getResult
  
  /**
   * Return error stream.
   * @return Error stream
   */
  public String getError(){
    return gpgError;
  }//getError
  
  /**
   * Run gnuPG commands.
   * @param cmd Commands to run in gpg
   * @param inputStr String to run
   * @return Success/failure
   */
  private boolean runGnuPG(String cmd, String inputStr){
    Process p;
    String commands = this.gpgRuntime + cmd;
    
    try{
      p = Runtime.getRuntime().exec(commands);
    }catch(IOException io){
      System.out.println("IO Error: " + io.getMessage()); // Output error
      return false;
    }
    
    ProcessStreamReader stdOut = new ProcessStreamReader(p.getInputStream());
    ProcessStreamReader stdErr = new ProcessStreamReader(p.getErrorStream());
    
    stdOut.start();
    stdErr.start();
    
    if(inputStr != null){
      BufferedWriter out = new BufferedWriter(new OutputStreamWriter(p.getOutputStream()));
      
      try{
        out.write(inputStr);
        out.close();
      }catch(IOException io){
        System.out.println("Exception at write: " + io.getMessage()); // output error
        return false;
      }
    }//if
    
    try{
      p.waitFor();
      stdOut.join();
      stdErr.join();
    }catch(InterruptedException i){
      System.out.println("Exception at wait for: " + i.getMessage()); // output error
      return false;
    }
    
    try{
      exitCode = p.exitValue();
    }catch(IllegalThreadStateException itse){
      return false;
    }
    
    gpgResult = stdOut.toString();
    gpgError = stdErr.toString();
    
    return true;
  }//runGnuPG
  
  /**
   * Run gnuPG commands.
   * @param cmd Commands to run in gpg
   * @param inputStr String to run
   * @return Success/failure
   */
  private boolean runGnuPG(String cmd, String inputStr, boolean batch){
    Process p;
    String commands = this.gpgRun + cmd;
    
    try{
      p = Runtime.getRuntime().exec(commands);
    }catch(IOException io){
      System.out.println("IO Error: " + io.getMessage()); // Output error
      return false;
    }
    
    ProcessStreamReader stdOut = new ProcessStreamReader(p.getInputStream());
    ProcessStreamReader stdErr = new ProcessStreamReader(p.getErrorStream());
    
    stdOut.start();
    stdErr.start();
    
    if(inputStr != null){
      BufferedWriter out = new BufferedWriter(new OutputStreamWriter(p.getOutputStream()));
      
      try{
        out.write(inputStr);
        out.close();
      }catch(IOException io){
        System.out.println("Exception at write: " + io.getMessage()); // output error
        return false;
      }
    }//if
    
    try{
      p.waitFor();
      stdOut.join();
      stdErr.join();
    }catch(InterruptedException i){
      System.out.println("Exception at wait for: " + i.getMessage()); // output error
      return false;
    }
    
    try{
      exitCode = p.exitValue();
    }catch(IllegalThreadStateException itse){
      return false;
    }
    
    gpgResult = stdOut.toString();
    gpgError = stdErr.toString();
    
    return true;
  }//runGnuPG
  
  /**
   * Create a temporary file
   * @param inStr Input stream.
   * @return Success/failure
   */
  private boolean createTempFile(String inStr){
    this.tmpFile = null;
    FileWriter fw;
    
    try{
      this.tmpFile = File.createTempFile("cfGnuPG", null);
    }catch(Exception e){
      System.out.println("Can't create temp file: " + e.getMessage()); // output error
      return false;
    }
    
    try{
      fw = new FileWriter(this.tmpFile);  // new fileWriter object
      fw.write(inStr);           // write file
      fw.flush();             // force buffered data out of output stream
      fw.close();             // close output stream
    }catch(Exception e){
      tmpFile.delete();          // delete temp file if there's an error
      System.out.println("Cannot write temp file: " + e.getMessage()); // output error
      return false;
    }
    return true;
  }//createTempFile
}//class GnupG 